#pragma once
#ifndef LIT__FACTORY__INCLUDED__HH__
#define LIT__FACTORY__INCLUDED__HH__

#include <unordered_map>
#include <LitString.h>

namespace Lit
{
	template<typename T>
	class Factory;

	template<typename T>
	class FactoryException : public std::runtime_error
	{
		Factory<T>*			mFactory;

	public:
		FactoryException( Factory<T> * factory, const String &error, const String &func = "", size_t line = 0, const String &file = "" ) : mFactory( factory ), std::runtime_error( build( factory, error ).data() ) {}
		FactoryException( const FactoryException& rk ) : std::runtime_error( rk ), mFactory( rk.mFactory ){}
		~FactoryException(){}

		Factory<T> * getFactory(){ return mFactory; }
		const Factory<T> * getFactory() const{ return mFactor; }

		static String build( Factory<T> * factory, const String &error ){
			String ret = "The factory<"+(factory?factory->getType():String("NULL"))+"> provoked the following error:\r\n" +
								"\t" + error;
			return ret;
		}
	};

	template<typename T>
	class FactoryItem
	{
		friend class Factory<T>;

		Factory<T> * mCreator;
		String mName;
	public:
		FactoryItem( const String &name, Factory<T> * creator ) : mName(name), mCreator(creator){ assert( creator && "Invalid Creator !" ); }
		~FactoryItem( ){}
		
		virtual String getType(){ return mCreator ? mCreator->getType() : ""; }
		const String &getName() const{ return mName; }

		operator T() const{ return *this; }

		inline void setCreator( Factory<T> * creator ){ mCreator = creator; }
		inline Factory<T> * getCreator(){ return mCreator; };
		inline const Factory<T> * getCreator() const{ return mCreator; };
	};
	
	template<typename T>
	class FactoryItemPtr
	{
		friend class Factory<T>;
	public:
		typename typedef T				BaseType;
		typename typedef FactoryItem<T> Type;

		FactoryItemPtr( BaseType * ptr ) : mPtr( ptr ){}
		FactoryItemPtr( ) : mPtr(NULL){}
		FactoryItemPtr( const FactoryItemPtr<T>& rk ) : mPtr(rk.mPtr){}
		//template<typename T2> FactoryItemPtr( const FactoryItemPtr<T2>& rk ) : mPtr(rk.mPtr){}
		virtual ~FactoryItemPtr(){}
		
		FactoryItemPtr<T>& operator=( const FactoryItemPtr<T>& rk ){ mPtr = rk.mPtr; return *this; }
		FactoryItemPtr<T>& operator=( T* rk ){ mPtr = rk; return *this; }
		
		bool operator==( const FactoryItemPtr<T>& rk ) const{ return mPtr == rk.mPtr; }
		bool operator!=( const FactoryItemPtr<T>& rk ) const{ return mPtr != rk.mPtr; }
		operator bool( ) const{ return mPtr != NULL; }
		
		Type * get(){ if( !mPtr ) throw FactoryException<T>( mPtr?mPtr->getCreator():NULL, "Cannot return FactoryItem, pointer is invalid", __FUNCTION__, __LINE__, __FILE__ ); return mPtr; }
		const Type * get() const{ if( !mPtr ) throw FactoryException<T>( mPtr?mPtr->getCreator():NULL, "Cannot return Item, pointer is invalid", __FUNCTION__, __LINE__, __FILE__ ); return mPtr; }
		
		Type * operator->(){ return get(); }
		const Type * operator->() const{ return get(); }

		Type * operator*(){ return get(); }
		const Type * operator*() const{ return get(); }

	protected:
		void destroy(){ if( mPtr ) delete mPtr; mPtr = NULL; };

		Type * mPtr;			
	};
		
	template<typename T>
	class Factory
	{
	public:
		typename typedef std::unordered_map<String, FactoryItemPtr<T>> ItemMap;
		typename typedef ItemMap::iterator ItemIter;
		typename typedef ItemMap::const_iterator ItemCIter;

	protected:
		void _destroy( ItemIter it )
		{
			if( it == mItems.end() ) return;
			if( it->second ) destroyImpl( (T*)*(it->second) );
			mItems.erase( it );
		}
		
		virtual T* createImpl( const String &name ) = 0;
		virtual void destroyImpl( T* itm ) = 0;

	public:
		Factory(const String &type)
			: mType( type )
		{
		}

		virtual ~Factory()
		{
			destroyResources();
		}

		const String &getType() const{ return mType; }
		FactoryItemPtr<T> getResource( const String &name ) const
		{
			for( auto i=mItems.begin(); i!=mItems.end(); i++ )
				if( i->getName() == name ) return *i;
			throw FactoryException( this, "Cannot return resource '"+name+"' because it doesn't exist", EXC_LOC );
			return NULL;
		}

		bool hasResource( const String &name ) const
		{
			for( auto i=mItems.begin(); i!=mItems.end(); i++ )
				if( i->getName() == name ) return true;
			return false;
		}
		
		FactoryItemPtr<T> findResource( const String &name ) const
		{
			for( auto i=mItems.begin(); i!=mItems.end(); i++ )
				if( i->getName() == name ) return *i;
			return NULL;
		}
		ItemIter findResource( FactoryItemPtr<T> )
		{
			for( auto i=mItems.begin(); i!=mItems.end(); i++ )
				if( i->getName() == name ) return i;
			return mItems.end();
		}
		ItemCIter findResource( FactoryItemPtr<T> ) const
		{
			for( auto i=mItems.begin(); i!=mItems.end(); i++ )
				if( i->getName() == name ) return i;
			return mItems.end();
		}

		FactoryItemPtr<T> createResource( const String &name ){ 
			T * t = NULL;
			try
			{
				t = createImpl( name ); 
			}
			catch( std::exception e )
			{
				if( t ) delete t, t = NULL;
				throw;
			}
			catch( ... )
			{
				if( t ) delete t, t = NULL;
				throw;
			}
			mItems[name] = t;
			return t;
		}
		void destroyResource( const String &name ){ 
			if( hasResource( name ) )
				_destroy( mItems.find(name) ); }

		void destroyResources()
		{
			while( !mItems.empty() )
			{
				_destroy( mItems.begin() );
			}
		}

	protected:
		ItemMap			mItems;
		String		mType;
	};
};

#endif